//-std=gnu++0x
// standard library headers 
#include <string>
#include <iostream>
#include <exception>
#include <vector>
#include <functional>

// boost headers
#include <boost/algorithm/string.hpp>
#include <boost/regex.hpp>
#include <boost/lexical_cast.hpp>

using boost::replace_all_copy;
using boost::iterator_range;
using boost::split;
using boost::is_any_of;
using boost::trim_left_copy_if;
using std::find;


#include <is_equal_to.h>
using std::string;
using std::vector;


//________________________________________________________________________________________
// 
// 	skip_if
// 
// 		utility function
// 		Advances iterator, ignoring positions which match pred
// 
//________________________________________________________________________________________
template<typename PRED>
string::const_iterator skip_if(string::const_iterator beg, string::const_iterator end, PRED pred)
{
	while (beg != end && pred(*beg))
		++beg;
	return beg;
}


//________________________________________________________________________________________
//
// 	indent_string
// 
//		indent all lines in string by specified number of spaces by substituting new lines
//
//________________________________________________________________________________________
string indent_string(const string & str, unsigned len = 8)
{
	return string(len, ' ') + replace_all_copy(str, "\n", "\n" + string(len, ' '));
}


//________________________________________________________________________________________
//
// 	split_leading_whitespace

//		get initial whitespace

//________________________________________________________________________________________ 
void split_leading_whitespace(	const string& str, 
								string& whitespace_str, 
								string& after_whitespace_str)
{
	
	boost::iterator_range<string::const_iterator> after_indent = boost::trim_left_copy_if(boost::make_iterator_range(str.begin(), str.end()), is_any_of(" \t"));
	whitespace_str = string(str.begin(), after_indent.begin());
	after_whitespace_str = string(after_indent.begin(), after_indent.end());
}

typedef boost::iterator_range<string::const_iterator> ci_pair;
typedef std::reverse_iterator<string::const_iterator> r_iter;

//________________________________________________________________________________________
// 
//	rfind_if

//		search from back

//________________________________________________________________________________________ 
template<typename PRED>
string::const_iterator rfind_if(string::const_iterator beg, string::const_iterator end, PRED pred)
{
	r_iter rbeg(end);
	r_iter rend(beg);
	r_iter rfound = std::find_if(rbeg, rend, pred);
	if (rfound == rend)
		return end;
	return (rfound+1).base();
}

//________________________________________________________________________________________ 
 
//	line_wrapped 

//		Wrap string to break up long lines into lines of line_len each
// 		Will wrap 
// 			1. removing spaces or tabs (subsequent lines will not begin with spaces/tabs)
// 			2. after punctuation
// 			3. forcibly at line_len if words are too long
// 
// 		If initial lines consisting of spaces only will remain unconsumed

//________________________________________________________________________________________ 
string line_wrapped(const string& str, unsigned line_len)
{
	// minimum line length of 1
	if (line_len == 0)
		line_len = 1;
	
	// construct matching regular expression
	string regex_str = "^(.{0," + boost::lexical_cast<std::string>(line_len - 1) + "}\\S)(\\s+)";
	//string regex_str = "^(.{1,40}\\S)(\\s+)";
	boost::regex whitespace_regex(regex_str);
	
	if (str.length() < line_len)
		return str;

	string::const_iterator beg = str.begin();
	string::const_iterator end = str.end();


	vector<string> wrapped_lines;
	// while still has line to be wrapped...
	while (beg + line_len < end)
	{
		string::const_iterator line_end = beg + line_len;
		
		//
		// 	space / tab delimited
		//
		boost::smatch found_match;
		
		if(boost::regex_search(beg, end, found_match, whitespace_regex))
		{	
			//std::cerr << "<1-" << found_match[2].second - found_match[2].first << "?>\n";
			//std::cerr << "<2-" << found_match[1].second - found_match[1].first << "?>\n";
			string matched_str(found_match[1].first, found_match[1].second);
			wrapped_lines.push_back(matched_str);
			beg = found_match[2].second;
			continue;
		}
		
		// 
		// punctuation delimited
		// 
		string::const_iterator found = rfind_if(beg, line_end, boost::is_punct());
		if (found != line_end)
		{
			// include punctuation mark at end of previous line
			wrapped_lines.push_back(string(beg, found + 1));
			beg = found + 1;
		}
		// 
		// All spaces and tabs: force wrap at line_len, and continues
		// 	without consuming more spaces on the next line
		// 
		else if (boost::is_any_of(" \t")(*beg))
		{
			wrapped_lines.push_back(string(beg, line_end));
			beg += line_len;
			continue;
		}
		// 
		// No spaces: Really long word, force wrap at line_len
		// 
		else
		{
			wrapped_lines.push_back(string(beg, line_end));
			beg += line_len;
		}

		// make sure next line does not start with spaces or tabs
		beg = skip_if(beg, end, boost::is_any_of(" \t"));
	}
	if (end != beg)
	{
		wrapped_lines.push_back(string(beg, end));
	}
	return boost::join(wrapped_lines, "\n");
	
}

//________________________________________________________________________________________ 
 
//	line_wrap_keeping_indent 

//		
// 		Indented lines will be wrapped to give indented paragraphs
// 		Tabs are not expanded. However, the tab_width is used to make sure
// 		that the lines are wrapped at the correct position (after accounting for
// 		tab expansion)

//________________________________________________________________________________________ 
string line_wrap_keeping_indent(const string & str, unsigned line_len, unsigned tab_width)
{
	vector<string> lines;
	// more verbose syntax using standard library
	//split(lines, str, std::bind1st(std::equal_to<char>(), '\n') );


	// tackle line by line
	split(lines, str, is_equal_to('\n') );
	for (unsigned i = 0; i < lines.size(); ++i)
	{
		string whitespace_str;
		string after_whitespace_str;
		split_leading_whitespace(	lines[i], whitespace_str, after_whitespace_str);
		string tab_expanded_indent = replace_all_copy(whitespace_str, "\t", string(tab_width, ' '));
		string line_wrapped_str = line_wrapped(after_whitespace_str, line_len - tab_expanded_indent.length());
		lines[i] = whitespace_str + replace_all_copy(line_wrapped_str, "\n", "\n" + whitespace_str);
	}
	return boost::join(lines, "\n");
	//is_from_range
}



#include <substr_ref.h>
using boost::replace_all;
using boost_helper::t_substr_ref;

string hanging_indent_with_line_wrap(const string& line, unsigned hanging_indent, unsigned line_len)
{
	string new_line = line;
	string hanging_indent_str = "\n" + string(hanging_indent, ' ');

	// current position (beginning of current line)
	unsigned pos = 0;

	// first line is not indented, so wrap_after_len is full line_len
	unsigned wrap_after_len = line_len;

	while (pos + wrap_after_len < new_line.length())
	{
		// default to wrapping after last character on line
		unsigned end_pos = pos + wrap_after_len;

		// any line after first line is indented, so wrap_after_len is shortened correspondingly
		wrap_after_len = line_len - hanging_indent;

		// enforce line break if encounter newline character
		unsigned break_point = new_line.find('\n',  pos);
		if (break_point <= end_pos && break_point != static_cast<unsigned>(string::npos))
		{
			// remove newline character and put break there
			new_line.erase(break_point, 1);
			end_pos = break_point;
		}

		// line break after last space if possible
		else
		{
			break_point = new_line.rfind(' ',  end_pos);
			if (break_point > pos && break_point != static_cast<unsigned>(string::npos))
			{
				// remove space character and put break there
				new_line.erase(break_point, 1);
				end_pos = break_point;
			}

			// line break after punctuation if possible
			else
			{
				break_point = new_line.find_last_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", end_pos);
				if (break_point > pos && break_point != static_cast<unsigned>(string::npos))
				{
					// put break after there
					end_pos = break_point + 1;
				}
			}
		}

		// insert line break and move along current position
		new_line.insert(end_pos, hanging_indent_str);
		pos = end_pos + hanging_indent + 1;
	}

	// replace last "\n"s with hanging_indent_strs
	if (pos > 1)
	{
		t_substr_ref<string> last(new_line, pos - 1);
		replace_all(last, "\n", hanging_indent_str);
	}

	return new_line;
}


